/*
 * ============================================================================
 * Licensed Materials - Property of IBM
 * Project  Zero
 *
 * (C) Copyright IBM Corp. 2007  All Rights Reserved.
 *
 * US Government Users Restricted Rights - Use, duplication or disclosure
 * restricted by GSA ADP Schedule Contract with IBM Corp.
 * ============================================================================
 * Copyright (c) 1999 - 2006 The PHP Group. All rights reserved.
 * ============================================================================
 */
package com.ibm.p8.engine.parser.custom;

//////////////////////////////////////////////////////////////////
//                                                              //
//   This file was auto-generated by the LPG Eclipse Tooling.   //
//   It is safe to edit this file. It will not be overwritten.  //
//                                                              //
//   You are strongly recommend to provide custom logic here.   //
//   If you decide to move this class to a different package,   //
//   make sure you fix Factory.java                             //
//                                                              //
//////////////////////////////////////////////////////////////////

import java.util.HashSet;

import org.jikes.lpg.runtime.BadParseException;
import org.jikes.lpg.runtime.BadParseSymFileException;
import org.jikes.lpg.runtime.DeterministicParser;
import org.jikes.lpg.runtime.IParser;

import com.ibm.p8.engine.ast.Astclass_name_reference;
import com.ibm.p8.engine.ast.Astclass_variable_declaration_element;
import com.ibm.p8.engine.ast.Astecho_expr_list;
import com.ibm.p8.engine.ast.Astfunction_call_parameter_list;
import com.ibm.p8.engine.ast.Astglobal_var;
import com.ibm.p8.engine.ast.Astisset_variables;
import com.ibm.p8.engine.ast.Astnon_empty_array_pair;
import com.ibm.p8.engine.ast.Astnon_empty_array_pair_ref;
import com.ibm.p8.engine.ast.Astnon_empty_function_call_parameter;
import com.ibm.p8.engine.ast.Astnon_empty_function_call_parameter_list;
import com.ibm.p8.engine.ast.Astnon_empty_static_array_pair;
import com.ibm.p8.engine.ast.Astsimple_indirect_reference;
import com.ibm.p8.engine.ast.Aststatic_var;
import com.ibm.p8.engine.ast.Asttop_statement_list_mandatory;
import com.ibm.p8.engine.ast.Astunset_variables;
import com.ibm.p8.engine.ast.Astunticked_statement;
import com.ibm.p8.engine.ast.Astvariable_name;
import com.ibm.p8.engine.ast.Astvariable_property;
import com.ibm.p8.engine.parser.core.PHPParsersym;
import com.ibm.p8.engine.parser.model.Ast;


/**
 * Custom parser to flatten specific Ast nodes and diagnose syntax errors.
 * 
 * @see com.ibm.p8.engine.parser.custom.Factory
 */
public class CustomParser extends com.ibm.p8.engine.parser.core.Parser {

	static final boolean DIAGNOSE = true;

	/**
	 * Constructor.
	 */
	public CustomParser() {
		setDebug(false); // print verbose info when rules are reduced
		setFlatten(true); // reduce Ast following rules below
	}

	/**
	 * Provide a list of notes which will get squashed out of the tree.
	 * @return A HashSet of nodes which will get flattened
	 */
	@SuppressWarnings("unchecked")
	public HashSet getAstNodesToFlatten() {
		HashSet nodesToFlatten = super.getAstNodesToFlatten();
		//
		//
		// Here you would normally list the nodes that represent a list,
		// I.e, rules of this form:
		//
		// ExpressionList ::= $empty | ExpressionList Expression
		// 
		// When FLATTEN == true, The following Ast nodes will be flattened into
		// their parent:
		// 1. Ast nodes with no children at all (i.e, reducing to $empty, aka.
		// epsilon nodes)
		// 2. Ast nodes with only one child (often generated in the process of
		// making
		// a grammar unambiguous, as JikesPG does not support operator
		// precedence).
		// 3. All the nodes reduced by rules for which the lhs is added to
		// 'nodesToIgnore'.
		//
		// Here are two examples:
		//
		// nodesToFlatten.add("ExpressionList");
		// nodesToFlatten.add("StatementList");
		//
		nodesToFlatten.add(Asttop_statement_list_mandatory.class);
		nodesToFlatten.add(Astvariable_property.class);
		return nodesToFlatten;
	}

	/**
	 * Provide a list of notes which will not get squashed out of the tree.
	 * @return A HashSet of nodes which will not get flattened
	 */
	@SuppressWarnings("unchecked")
	public HashSet getAstNodesToNotFlatten() {
		HashSet nodesToNotFlatten = super.getAstNodesToNotFlatten();
		//
		//
		// Here you would normally list the nodes that are too 
		// complicated to flatten, and we want to keep them intact.
		//
//		nodesToNotFlatten.add(Astarray_pair_list.class);
		nodesToNotFlatten.add(Astnon_empty_array_pair.class);
		nodesToNotFlatten.add(Astnon_empty_array_pair_ref.class);
		nodesToNotFlatten.add(Astfunction_call_parameter_list.class);
		nodesToNotFlatten.add(Astnon_empty_function_call_parameter_list.class);
		nodesToNotFlatten.add(Astnon_empty_function_call_parameter.class);
		nodesToNotFlatten.add(Astnon_empty_static_array_pair.class);
		nodesToNotFlatten.add(Astunset_variables.class);
		nodesToNotFlatten.add(Astisset_variables.class);
		nodesToNotFlatten.add(Astclass_variable_declaration_element.class);
		nodesToNotFlatten.add(Astsimple_indirect_reference.class);
		nodesToNotFlatten.add(Astvariable_name.class);
		nodesToNotFlatten.add(Aststatic_var.class);
		nodesToNotFlatten.add(Astecho_expr_list.class);
		// Ensure unticked statements with 1 child (inline html and void) don't get flatted:
		nodesToNotFlatten.add(Astunticked_statement.class);
		// Ensure parent always sees Astclass_name_reference as child type, rather than having to branch based on whether
		// child type is T_STRING or dynamic_class_name_reference
		nodesToNotFlatten.add(Astclass_name_reference.class);
		// Ensure simple global_var declaration is not flattened.
		nodesToNotFlatten.add(Astglobal_var.class);
		return nodesToNotFlatten;
	}

	/**
     * @return IParser - the parser interface
     * @throws BadParseSymFileException a symbol file is missing
      */
 	public IParser createParser() throws BadParseSymFileException {
		// The parser could be a BacktrackingParser, but we use the
		// Deterministic version for lookahead.
		return new DeterministicParser(prsStream, prs, this); 
	}

	/**
	 * Get the name of the symbol which caused an error.
	 * @param index - the index in the symbol table
	 * @return the symbol name.
	 */
	private String symbolName(int index) {
		return prs.name(index);
	}

	/**
	 * Get the index for a terminal at a location in the ParserTable.
	 * @param index the index in the symbol table
	 * @return the index of a terminal
	 */
	public int terminalIndex(int index) {
		return prs.terminalIndex(index);
	}

	/**
	 * Get an element from the list of possible expected symbols.
	 * @param index the index in the symbol table.
	 * @return the index of a possible next symbol.
	 */
	public int asr(int index) {
		return prs.asr(index);
	}

	/**
	 * Get the current symbol in case of an error.
	 * @param index the index in the symbol table.
	 * @return the number of the symbol found.
	 */
	public int asi(int index) {
		return prs.asi(index);
	}

	/**
	 * Parse the input. If parsing failed, start an error diagnoser to find out
	 * what can be done to fix the apparent syntax error. The diagnoser will
	 * call reportError methods in class CharStream. You should fix those to
	 * specialize error handling.
	 * <p>
	 * If parsing was successful, the full parse tree is returned. See
	 * getAstNodesToIgnore() to reduce the size of the generated Ast tree.
	 * @return an AST node at the head of a syntax tree, or null
	 * 
	 */
	public Ast parse() {
		try {
			Ast.setTotalNodes(0);
			lastErrorToken = null;
			lpgParser = createParser();
		} catch (BadParseSymFileException e) {
			prsStream.reportError(0, "BadParseSymFileException");
			return null;
		}
		try {
			Ast result = (Ast) lpgParser.parse();
			if (prsStream.index < prsStream.tokens.size() - 1) {
				// This is the case where we find an EOF symbol , but are not at the
				// end of the input stream.
				lastErrorToken = prsStream.getTokenAt(prsStream.index);
				msgKey = sPARSERERROREOF;
				inserts = null;
				lineNumber = 0;
				if (this.scanner.fileName != null) {
					fileName = scanner.fileName;
				} else {
					fileName = "<unknown>";
				}
				return null;
			}
			return result;
		} catch (BadParseException e) {

			lastErrorToken = prsStream.getTokenAt(e.error_token);
			if (lastErrorToken.getKind() == PHPParsersym.$eof) {
				// This is the case where we reach the end of the file, but expect
				// more input. 
				msgKey = sPARSERERROREOF;
				inserts = null;
			} else {
				msgKey = sPARSERERRORSYNTAX;

				// count the possible alternatives without resolving their names
				// if there are more than a certain number, we will not print them,
				// so dont need the overhead of resolving them.
				
				final int alternativeMax = 3;
				int altCount = 0;
				int symbol = 0;

				for (int i = asi(e.last_state); asr(i) != 0; i++) {
					symbol = asr(i);
					// Always skip these two tokens as they are not user-specified
					if (symbol != prs.getEoftSymbol()
							&& symbol != prs.getErrorSymbol()) {
						altCount++;
						if (altCount > alternativeMax) {
							altCount = 0;
							break;
						}
					}
				}

				// The inserts contain the current token, and the list of alternatives
				inserts = new Object[1 + altCount];

				inserts[0] = "'" + lastErrorToken.toString() + "'";

				if (inserts[0].equals("''")) {
					inserts[0] = lastErrorToken.getTokenKindName();
				}

				if (altCount > 0) {
					
					// we are here becuase we have alternatives, and we know that there are
					// up to MAX_ALTERNATIVES in the list.
					int index = 1;
					boolean found = false;

					for (int i = asi(e.last_state); asr(i) != 0; i++) {
						found = false;
						symbol = asr(i);
						if (symbol != prs.getEoftSymbol()
								&& symbol != prs.getErrorSymbol()) 	{
							// resolve the symbol to its name using the scanner tables.
							// TODO We should generate the scanner, and inherit the symbols
							// in the parser from it, then the table would match and this
							// would be an index, not a search.
							for (int j = 0; j < PHPScanner.KEYWORDS.length; j++) {
								if (PHPScanner.KEYWORD_VALUES[j] == symbol) {
									inserts[index++] = "'"
											+ new String(PHPScanner.KEYWORDS[j])
											+ "'";
									found = true;
								}
							}
							// we didnt find it in the scanner, so its something like T_VARIABLE
							if (!found) {
								// symbol is a candidate and its name is
								// name(terminalIndex(symbol))
								inserts[index++] = symbolName(terminalIndex(symbol));
							}
						}
					}
				}
			}
			// finally - find the filename. If this is an 'eval' then it has no file name
			// but the parent scanner does. Search up until we find a valid file, or give
			// up.
//			Scanner sc = this.scanner;
			
			fileName = scanner.fileName;
			
//	   		while (sc.fileName == null
//    				&& sc.token != null) {
//    			fileName = sc.token.getScanner().fileName;
//    			
//    			lineNumber = sc.token.getLine();
//    			fileName += "(" + Integer.toString(lineNumber) + ") : eval()'d code";
//    			sc = sc.token.getScanner();
// 			}
    		
    		lineNumber = lastErrorToken.getLine();
 		}
		return null;
	}

	/**
	 * Set the symbol for a given reduction. Can be used to track reductions.
	 * When this method is called the Ast node is already complete (including
	 * children and a reference to the rule that produced the Ast node).
	 * @param node the node to use.
	 */
	public void setSym1(Ast node) {
		super.setSym1(node);
 	}
	

}
